#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009, Kao Cardoso Félix
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
#   copyright notice, this list of conditions and the following
#   disclaimer in the documentation and/or other materials provided
#   with the distribution.
# * Neither the name of the python-game-tools nor the names of its
#   contributors may be used to endorse or promote products derived
#   from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""A simple level editor.
"""

import os
from collections import defaultdict

import pyglet
from pyglet.window import key
from pyglet.window import mouse
from pyglet.gl import *

import nodesource
import actions
from vec2 import Vec2

from util import make_gradient
from level import Level, Node

num_keys = [key._0,
            key._1,
            key._2,
            key._3,
            key._4,
            key._5,
            key._6,
            key._7,
            key._8,
            key._9,]

mods_filter = key.MOD_CTRL|key.MOD_ALT|key.MOD_SHIFT

class Camera(object):
    """The editor 2D camera.
    """

    def __init__(self, window):
        """Create a camera for the given window.
        """
        self.window = window
        self.pos = Vec2(0, 0)
        self.speed = 500.0
        self._zoom = 1.0
        self.zoom_speed = 5.0

        ## KeyState for camera movement
        self.key_state = key.KeyStateHandler()
        window.push_handlers(self.key_state)

        pyglet.clock.schedule(self.check_input)

    def _set_zoom(self, value):
        self._zoom = value
        if self._zoom < 0.01:
            self._zoom = 0.01

    def _get_zoom(self):
        return self._zoom

    zoom = property(_get_zoom, _set_zoom)

    def transform(self):
        """Apply the camera transformation.
        """
        half_win = Vec2(self.window.width/2, self.window.height/2)
        t = (self.pos + half_win)

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        glTranslatef(half_win.x, half_win.y, 0.0)
        glScalef(self.zoom, self.zoom, 1.0)
        glTranslatef(-t.x, -t.y, 0.0)

    def screen_to_world(self, x, y):
        """Converts from screen to world coordinates.
        """
        half_win = Vec2(self.window.width/2, self.window.height/2)
        spos = Vec2(x, y)

        return ((spos - half_win) / self.zoom ) + (self.pos + half_win)

    def check_input(self, dt):
        """Update method called at each frame.
        """
        dx, dy = 0, 0
        if self.key_state[key.W]:
            dy = 1
        elif self.key_state[key.S]:
            dy = -1

        if self.key_state[key.D]:
            dx = 1
        elif self.key_state[key.A]:
            dx = -1

        if dx != 0 or dy != 0:
            vel = Vec2(dx, dy).normalize() * self.speed * dt
            self.pos = self.pos + vel

        if self.key_state[key.DOWN]:
            self.zoom -= self.zoom_speed * dt
        elif self.key_state[key.UP]:
            self.zoom += self.zoom_speed * dt


class EditorHUD(object):
    """Display info about the editor.
    """
    style = {
        'font_size':10,
        'bold':True,
        }

    def __init__(self, editor):
        """Create a HUD for the given editor instance.
	"""
        self.editor = editor
        self.labels = {
            "layer": pyglet.text.Label(anchor_y="top", **self.style),
            "zoom": pyglet.text.Label(anchor_y="top",anchor_x="right", **self.style),
            }

    def update(self):
        """Updates HUD info.
        """
        window = self.editor.window
        layer_index = self.editor._current_layer_index
        layer_label = self.labels["layer"]

        layer_label.text = "Layer %d" % layer_index
        layer_label.x = 5
        layer_label.y = window.height - 5

        zoom_level = self.editor.camera.zoom
        zoom_label = self.labels["zoom"]

        zoom_label.text = "Zoom Level: %d%%" % int(zoom_level * 100)
        zoom_label.x = window.width - 5
        zoom_label.y = window.height - 5

    def draw(self):
        """Draw the HUD.
        """
        self.update()
        for label in self.labels.values():
            label.draw()


class Editor(object):
    """Level editor application.
    """

    def __init__(self, window, options):
        """Create an editor with the given options
	"""
        # super(Editor, self).__init__()
        self.window = window
        self.window.push_handlers(self)

        self._current_layer_index = 0
        self.hover_node = None

        self.bg_gradient = make_gradient((253, 127, 130, 255),
                                         ( 28,  28, 204, 255))

        self.node_source = nodesource.NodeSource(options.resource_path)
        self.hud = EditorHUD(self)

        # The current edit
        self.action = None

        self.camera = Camera(self.window)

        ## Level creation / loading
        self.level = Level()

        self.filename = options.filename
        if not self.filename: raise RuntimeError("No file specified.")
        try:
            f = file(self.filename, 'r')
            self.level.load(f)
        except IOError:
            pass # New level will be created

        ## Key Configs
        self.config_keys()

    @property
    def current_layer(self):
        return self.level.layers[self._current_layer_index]

    def config_keys(self):
        """Configure key bindings for the editor.
        """
        ## Key Configs

        self.mouse_drag_mappings = defaultdict(lambda:None,{
            (mouse.LEFT, 0) : actions.NodeMove,
            (mouse.RIGHT, 0) : actions.NodeRotate,
            (mouse.RIGHT, key.MOD_SHIFT) : actions.NodeScale
            })

        self.mouse_click_mappings = defaultdict(lambda:None,{
            (mouse.LEFT, 0) : self.create_new_node,
            })

        def hover_node_action(ActionType, *args):
            def callback():
                if self.hover_node:
                    action = ActionType(*args)

            return callback


        self.key_press_mapping = defaultdict(lambda:None,{
            # Editor commands
            (key.Z, key.MOD_CTRL) : actions.stack.undo_last,
            (key.S, key.MOD_CTRL) : lambda : self.level.save(open(self.filename, "w")),
            (key.F1, 0) : lambda : self.window.set_fullscreen(not self.window.fullscreen),
            (key.R, 0) : lambda : setattr(self.camera, 'zoom', 1.0),

            # Node commands
            (key.DELETE, 0) : hover_node_action(actions.NodeDeleteAction, self),

            (key.Z, 0) : hover_node_action(actions.NodeReorder, self, None), # Node to top
            (key.X, 0) : hover_node_action(actions.NodeReorder, self, 0), # Node to bottom

            (key.U, 0) : self.node_opacity_up,
            (key.I, 0) : self.node_opacity_down,
            })

    def create_new_node(self, mpos):
        """Creates a new node on the given position.
        """
        if not self.hover_node:
            self.do_action(actions.NodeCreateAction, self, mpos)

    def node_opacity_up(self):
        """Makes the node more opaque.
        """
        if self.hover_node:
            self.do_action(actions.NodeOpacityChange, self.hover_node, 15)

    def node_opacity_down(self):
        """Makes the node more transparent.
        """
        if self.hover_node:
            self.do_action(actions.NodeOpacityChange, self.hover_node, -15)

    def do_action(self, ActionType, *params):
        """Do an action of ActionType with the given params.
        """
        action = ActionType(*params)

    def draw_background(self):
        """Draw the gradient background.
        """
        glPushMatrix()
        glScalef(self.window.width, self.window.height, 0.0)
        self.bg_gradient.draw(GL_QUADS)
        glPopMatrix()

    # Event handling methods
    def on_draw(self):
        """Draw everything.
        """
        self.window.clear()
        self.draw_background()

        # Camera transformed stuff
        glPushMatrix()
        self.camera.transform()
        self.level.draw()

        if self.hover_node:
            self.hover_node.draw_bounds()

        glPopMatrix()
        # End camera transformed stuff

        if not self.hover_node:
            self.node_source.draw_brush(self.camera.zoom)

        self.hud.draw()

    def on_mouse_drag(self, x, y, dx, dy, buttons, mods):
        """Mouse drag continue the current edit, if any.
        """
        if self.action:
            mpos = self.camera.screen_to_world(x, y)
            self.action.update(mpos)

        else:
            node = self.hover_node
            ActionType = self.mouse_drag_mappings[buttons, mods & mods_filter]

            if node and ActionType:
                click_pos = self.camera.screen_to_world(x, y)
                self.action = ActionType(node, click_pos)
            else:
                self.action = None

    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
        """Mouse wheel changes the current tile.
        """
        print scroll_y
        if scroll_y < 0:
            self.node_source.previous_node()
        else:
            self.node_source.next_node()

    def on_mouse_motion(self, x, y, dx, dy):
        """
        """
        mpos = self.camera.screen_to_world(x, y)
        self.hover_node = self.current_layer.query_pos(mpos)
        if self.hover_node:
            self.window.set_mouse_visible()
        else:
            self.window.set_mouse_visible(False)

        self.node_source.on_mouse_motion(x,y,dx,dy)

    def on_mouse_release(self, x, y, button, mods):
        """Mouse click.
        """
        if self.action: self.action = None

        mpos = self.camera.screen_to_world(x, y)

        command = self.mouse_click_mappings[button, mods & mods_filter]
        if command:
            command(mpos)

    def on_key_release(self, symbol, mods):
        """Handle key commands.
        """
        command = self.key_press_mapping[symbol, mods & mods_filter]
        if command:
            command()

        # Select layer
        try:
            self._current_layer_index = num_keys.index(symbol)
        except ValueError:
            pass

    def run(self):
        """Run the editor
        """
        pyglet.app.run()


def main():
    """Script main function
    """
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option('-f', '--filename',
                      help='File to edit. If it doesn\'t '
                           'exist it will be created')
    parser.add_option('-R', '--resource-path',
                      help='Dir to look for resources.',
                      action='append')
    (options, args) = parser.parse_args()

    win = pyglet.window.Window()
    Editor(win, options).run()


if __name__ == "__main__":
    main()
